import os
import stat
import socket

import ConfigParser

from pyparsing import alphas, alphanums, removeQuotes, nums
from pyparsing import quotedString, oneOf, lineno, hexnums
from pyparsing import Suppress, Word, Optional, Keyword
from pyparsing import StringEnd, Literal, ParseFatalException
from pyparsing import ParseException, Group

from venya.lib import exception, ip, stdlib
from base import GypsumError

kword = lambda value: Keyword(value).suppress()

portMax = (2 << 15) - 1

icmp4 = {
        "echo-reply" : 0,
        "echorep" : 0,
        "unreachable" : 3,
        "unreach" : 3,
        "source-quench" : 4,
        "squench" : 4,
        "redirect" : 5,
        "alternate-address" : 6,
        "echo-request" : 8,
        "echo" : 8,
        "echoreq" : 8,
        "router-advertisement" : 9,
        "router-solicitation" : 10,
        "time-exceeded" : 11,
        "timex" : 11,
        "parameter-problem" : 12,
        "timestamp-request" : 13,
        "timestamp-reply" : 14,
        "information-request" : 15,
        "information-reply" : 16,
        "mask-request" : 17,
        "mask-reply" : 18,
        "trace" : 30,
        "traceroute" : 30,
        "conversion-error" :  31,
        "mobile-redirect" : 32,
        "ipv6-where" : 33,
        "ipv6-here" : 34,
        "mobregreq" : 35,
        "mobregrep" : 36,
        "skip" : 39,
        "photuris" : 40,
        }

portMapping = {
        "dns" : "domain",
        "ike" : "isakmp",
        "http" : "www",
        "mail" : "smtp",
        "dhcp" : "bootpc",
        "msa" : "submission",
        }

def validateport(port):
    retval = 0
    if isinstance(port, str):
        if port.isalpha():
            if port in portMapping.keys():
                port = portMapping[port]

            try:
                if port not in ("netconf", "traceroute"):
                    retval = socket.getservbyname(port)
            except socket.error as err:
                raise ValueError(err)
            else:
                retval = port
        elif port.isdigit():
            port = int(port)
            if port <= 0 or port > portMax:
                raise ValueError("invalid port number")
            else:
                retval = port
    elif isinstance(port, (int, long)):
        if port <= 0 or port > portMax:
            raise ValueError("invalid port number")
        else:
            retval = port

    return retval

def sortaddress(pfl):
    retval = {}
    prefix = []
    address = []
    for ix in pfl:
        if isinstance(ix, str):
            prefix.append(ix)
        elif isinstance(ix, ip.ip):
            address.append(ix)

    if len(prefix):
        retval["prefix"] = prefix

    if len(address):
        retval["address"] = address

    return retval

def addlinenumber(function):
    def wrapper(*args, **kwargs):
        function(*args, **kwargs)
        args[0].line = lineno(args[2], args[1])

    return wrapper

def validateIPAddr(string, location, tokens):
    retval = None
    try:
        retval = ip.ip(tokens[0])
    except exception.IPError as err:
        raise ValueError(err)

    return retval

def validateExpandFile(string, location, tokens):
    line = lineno(location, string)
    retval = None
    filename = tokens[0]
    if filename:
        parser = ConfigParser.SafeConfigParser()
        if filedir and os.path.exists(filedir):
            parser.read(filedir)
        else:
            raise ValueError("config file %s not found" %filedir)

        policydir = os.path.expandvars(parser.get("main", "policydir"))
        if policydir:
            filename = "%s/%s" %(policydir, filename)

    if not os.path.exists(filename):
        raise ValueError("file %s does NOT exist, line %d" %(filename, line))
    else:
        mode = os.stat(filename).st_mode
        if stat.S_ISREG(mode):
            retval = filename
        else:
            raise ValueError("%s invalid file, line %d" %(filename, line))

    return retval

def addStmt(string, option):
    retval = None
    if string and option:
        retval = Group(Keyword(string) + eq + option)
    else:
        raise ValueError("INTERNAL ERROR, invalid string/option")

    return retval

def parser(filename, grammar):
     results = None
     try:
         with open(filename, "r") as fh:
             results = grammar.parseString(fh.read())
     except ParseFatalException as err:
         raise GypsumError("parsing failed on %s: %s" %(filename, str(err)))
     except ParseException as err:
         raise GypsumError("syntax error on line %d \"%s\" in file %s"
                 %(err.lineno, err.line.strip(), filename))
     except ValueError as err:
         raise GypsumError("%s in file %s" %(str(err), filename))

     return results

lb, rb = map(Suppress, "{}")
lp, rp = map(Suppress, "()")
lbk, rbk = map(Suppress, "[]")
lt, gt = map(Suppress, "<>")

comma = Literal(",").suppress()
fs = Literal("/").suppress()
eq = Literal("=").suppress()
colon = Literal(":").suppress()

identifier = Word(alphas + alphanums + "-_")
filename = Word(alphas + alphanums + "$/-_.").setParseAction(validateExpandFile)
quoted = quotedString.copy().setParseAction(removeQuotes)
integer = Word(nums)
target = oneOf("junos junos-srx")
action = oneOf("accept reject next deny reject-with-tcp-rst")
comment = Optional(addStmt("comment", quoted))
ipaddr = Word(nums + "./").setParseAction(validateIPAddr)
ip6addr = Word(hexnums + ":/").setParseAction(validateIPAddr)
filedir = None
